https://blog.csdn.net/jesonjoke/column/info/21011 [Jeff丶Osmond
https://suprisemf.github.io/ 红宝石星球
学习资料汇总


1 并发编程的基础


2 并发基础
2-1 CPU多级缓存-缓存一致性




定义
1 | cpu缓存是位于CPU与内存之间的临时存储器,它的容量比内存小的多,但是交换速度却比内存要快得多 |
2-2CPU多级缓存-缓存一致性(MESI)
用于保证多个CPU cache之间缓存共享数据的一致,也可以通过给总线加LOCK锁的方式来保证缓存一致性
每个缓存行有如下M、E、S、I 四种状态

M(Modified):被修改,该缓存行只被缓存在该CPU的缓存中,并且是被修改过的,目前与主存的数据不一致,即该缓存需要在未来的某个时间点(并且允许其他CPU读取该主存中相应内存之间)写回主内存,当被写回主存之后,该缓存行的状态会变成独享(Exclusive)状态
E(Exclusive):独享的,该缓存行只被缓存在该CPU的缓存中,它是未被修改过的,与主存中的数据一致。该状态可以在任何时刻当有其它CPU读取该内存时变成共享状态。同样地,当CPU修改该缓存行中内容时,该状态可以变成Modified状态
S(Shared):共享的,该状态意味着该缓存行可能被多个CPU缓存,并且各个缓存中的数据与主存数据一致,当有一个CPU修改该缓存行时,其他CPU中对该缓存行的缓存可以被作废,变为无效状态(Invalid)
I(Invalid):该缓存时无效的
从CPU读写角度来说:
CPU读请求:缓存处于M、E、S状态都可以被读取,I状态CPU只能从主存中读取数据
CPU写请求:缓存处于M、E状态才可以被写。对于S状态的写,需要将其他CPU中缓存行置为无效才可写
主流解决方法:
通过总线加锁方法;
通过缓存一致性协议。
方式一,通过给总线加锁(数据、控制、地址总线),者会阻塞其他CPU对其他组件的访问,效率低下。
采用方式二。
最出名的就是Intel 的MESI协议,MESI协议保证了每个缓存中使用的共享变量的副本是一致的。它核心的思想是:
当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。
单核Cache中每个Cache line有2个标志:dirty和valid标志,它们很好的描述了Cache和Memory(内存)之间的数据关系(数据是否有效,数据是否被修改),而在多核处理器中,多个核会共享一些数据,MESI协议就包含了描述共享的状态。
在MESI协议中,每个Cache line有4个状态,可用2个bit表示,它们分别是:


2-3CPU多级缓存-乱序执行优化

1 | ## 处理器为提高运算速度而做出违背代码原有顺序的优化。当然了在正常情况下是不对结果造成影响的。在单核时代处理器对结果的优化保证不会远离预期目标,但是在多核环境下却并非如此。为什么这么说呢?首先,在多核条件下会有多个核执行指令,因此每个核的指令都有可能会乱序。另外处理器还引入了L1、L2缓存机制,这就导致了逻辑上后写入的数据不一定最后写入。 |
2-3 JAVA内存模型



















概述
已知多核CPU对于代码指令的乱序执行存在跟我们预期结果不一致的问题,解决方法就是使用Java内存模型规范对多线程操作进行约束。
其中Java内存模型是为屏蔽掉不同操作系统,不同硬件设备的差异,使Java程序对于多线程的环境有相同的执行结果,而对Java虚拟机(JVM)与硬件设备交互协调的规范
Java内存模型(JMM)
JMM与硬件结构如下图:
JMM内部抽象结构如下图:
堆区(Heap):是运行时数据区,并由GC(Garbage Collection)负责,可以在运行时动态分配内存大小,也不必通知编译器。缺点:由于运行时动态分配内存,故存取速度稍慢
静态成员变量跟随类的定义一起存放在堆中。堆中的对象可以被对该对象持有引用的线程访问,且其成员变量也可以被访问;当多个线程对同一对象的同一方法进行访问时,其实每个线程都有对该对象的数据的私有拷贝,而不会出现混乱。
栈区(Stack):主要存放8种基本类型的变量和句柄,JMM要求调用栈的本地变量存放在本地线程栈中,但对象是存放在堆中;优点:存取速度快(相对于Heap),仅次于寄存器,其中的数据可以共享;缺点:其中的数据与其生存期是确定的,缺乏灵活性。
本地变量(local variable)也可能是一个引用变量,指向一个变量的引用,此时引用变量是存放在线程栈(Thread Stack)中,但被引用变量仍然存放在堆Heap中。
同步的八种操纵
- lock(锁定):作用于主内存的变量,把一个标示为一条线程独占状态;
- unloc(解锁):作用于主内存的变量,把一个处于锁定状态的变量释放;
- read(读取):作用于主内存的变量,把一个变量从主内存传输到编程的工作内存,供随后的load操作使用;
- load(载入):作用于主内存的变量,它把read操作从主内存得到的变量值放入工作内存的变量副本中;
- use(使用):作用于主内存的变量,把工作内存的一个变量值传递给执行引擎;
- assign(赋值):作用于主内存的变量,它把一个从执行引擎收到的值赋值给工作内存的变量;
- store(存储):作用于主内存的变量,将工作内存的一个变量的值传递给主内存中,供随后的write操作使用;
- write(写入):作用于主内存的变量,将store操作从内存中的一个变量值传送到主内存的变量中。
同步的规则
同步操作与规则如下图:
- 如果把一个变量从主内存中复制到工作内存,需要按顺序执行read和load操作;如果把变量从工作内存同步回主内存中,需要按顺序执行store和write操作。需要按照先后顺序,但不要求连续执行!
- 不允许read和load、store和write操作之一单独出现。
- 不允许一个线程丢弃它的最近assign的操作,即变量在工作内存中改变了之后必须同步到主内存中。
- 不允许一个线程无原因地(没有发生过任何assign操作)把数据从工作内存同步到主内存中。
- 一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量。即对一个变量执行use或store操作之前,必须先执行assign或load操作。
- 一个变量在同一时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次;多次执行lock后,只有执行相同次数的unlock后,变量才会被解锁。即lock和unlock必须成对出现。
- 如果对一个变量执行lock操作,将会清空工作内存中此变量的值;在执行引擎使用这个变量前需要重新执行load或assign操作初始化变量的值。
- 如果一个变量事先没有被lock操作锁定,则不允许对它执行unlock操作;也不允许去unlock一个被其他线程锁定的变量。
- 对一个变量执行unlock操作之前,必须先把此变量同步到主内存中(执行store和write操作)。


